package com.colin.userservice.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.crypto.digest.MD5;
import com.colin.bean.User;
import com.colin.response.R;
import com.colin.response.RCM;
import com.colin.userservice.exception.RegisterException;
import com.colin.userservice.exception.sub.PasswordErrorException;
import com.colin.userservice.exception.sub.UserNotExistsException;
import com.colin.userservice.exception.sub.UsernameExistsException;
import com.colin.userservice.mapper.UserMapper;
import com.colin.userservice.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private Snowflake snowflake;

    @Autowired
    private UserMapper userMapper;

    /**
     * 校验用户名是否存在
     *
     * 根据用户名 去数据库查询数据
     * 如果能查到id 意味着存在数据  用户名不可以使用
     * 如果查不到意味着不存在数据  用户名可以使用
     * @param username 用户名
     * @return
     */
    @Override
    public R<Boolean> checkUsernameIsExist(String username) {

        // 多层if else 尽可能通过return 快速返回的方式 减少嵌套
        // 业务接口通过 抛出自定义异常 + 全局异常处理 既做到快速中断方法 也能做 问题或者异常的响应的统一处理
        Long id = userMapper.selectIdByUsername(username);

        // 用户名已被使用
        if (id != null) {
            log.error("用户名 : {} 已被使用", username);
            throw new UsernameExistsException(username);
        }

        return R.ok(true);
    }

    /**
     * 二次加盐加密
     * @param user
     * @return
     */
    @Override
    public R<Boolean> doRegister(User user) {

        // 雪花算法生成全局id
        long id = snowflake.nextId();
        user.setId(id);
        log.info("雪花算法生成id : {}", id);

        // 用户名
        String username = user.getUsername();
        // 一次加密后的密码
        String password = user.getPassword();
        log.info("接收到的前端密码 : {}", password);

        // 生成随机的salt
        RandomGenerator randomGenerator = new RandomGenerator(6);
        String salt = randomGenerator.generate();
        log.info("盐 : {}", salt);
        // 二次加盐加密  password:salt ->  md5(password:salt)
        String passwordTemp = password + ":" + salt;
        MD5 md5 = MD5.create();
        String finalPassword = md5.digestHex16(passwordTemp, StandardCharsets.UTF_8);
        log.info("最终密码 : {}", finalPassword);

        // 数据库中的datetime对应的java中的数据类型是 LocalDateTime
        LocalDateTime now = LocalDateTime.now();

        // 封装user数据
        user.setPassword(finalPassword);
        user.setSalt(salt);
        user.setAvatarUrl(null);
        user.setSignupTime(now);
        user.setLevel(1);
        user.setExp(0);
        user.setDescription("这个人很懒，还没有个性签名");
        user.setSex("男");
        user.setBirthday(null);
        user.setAddress(null);

        Boolean insertResult = userMapper.insertUser(user);

        if (!insertResult) {
            log.error("用户名 : {} 注册失败 结果为false 抛出异常 RegisterException", username);
            throw new RegisterException(RCM.USER_REGISTER_ERROR.getMessage());
        }

        return R.ok(insertResult);
    }

    /**
     * 登录逻辑
     * 1. 验证当前用户是否能登录 （能不能查出用户）
     *
     * 2. 能登录的情况下 服务端怎么做去保存用户登录态
     * 长短Token - SaToken
     * @param user
     * @return
     */
    @Override
    public R<User> doLogin(User user) {
        // 1. 利用用户名的唯一性去得到 salt  这样才能在后续计算得到最终密码 才能去做username 和password 的比对
        String username = user.getUsername();
        String salt = userMapper.selectSaltByUsername(username);
        log.info("用户名 : {} 对应的 salt : {}", username, salt);

        if (salt == null) {
            log.error("用户名为 : {} 的用户不存在", username);
            throw new UserNotExistsException(RCM.USER_NOT_EXISTS.getMessage());
        }

        // 2. 二次加盐加密计算最终密码
        String passwordTemp = user.getPassword() + ":" + salt;
        MD5 md5 = MD5.create();
        String finalPassword = md5.digestHex16(passwordTemp, StandardCharsets.UTF_8);
        log.info("最终密码 : {}", finalPassword);

        // 3. 使用 username 和 finalPassword 进行查询
        user = userMapper.selectUserByUsernameAndPassword(username, finalPassword);

        if (user == null) {
            log.error("用户名 : {} 密码错误", username);
            throw new PasswordErrorException(RCM.USER_PASSWORD_ERROR.getMessage());
        }

        // 4. 处理敏感数据
        user.setPassword(null);
        user.setSalt(null);

        // 5. 服务端保存登录态
        // StpUtil.login(传入一个和当前登录用户相关的唯一标识即可)
        StpUtil.login(user.getId());

        return R.ok(user);
    }
}
